package fms_test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

public class DistanceSet {

	private String source;
	private String tmsource;
	
	private int lenDiff;
	private int editDist;
	private int lensourcePhrase;
	private int lentmsourcePhrase;
	
	private double maxFMS;
	
	private ArrayList<String> sourceListPhrase;
	private ArrayList<String> tmsourceListPhrase;
	private ArrayList<String> sourceListChar;
	private ArrayList<String> tmsourceListChar;
	private ArrayList<String> editHistory;
	
	private int[][] distanceMat;
	
	
	/**
	 * @param sourceSystem.out.println("i: "+i+" ind: "+ind);
	 * @param TMsource
	 */
	

	public DistanceSet(String source, String TMsource){
		
		this.source = source;
		this.tmsource = TMsource;
		
		/**
		 * simple but functional string preprocessing
		 */
		String[] sourceWords = this.source.split(" ");
		String[] tmsourceWords = this.tmsource.split(" ");
		 
		this.sourceListPhrase = new ArrayList<String>(Arrays.asList(sourceWords));
		this.tmsourceListPhrase = new ArrayList<String>(Arrays.asList(tmsourceWords));
		
		this.lensourcePhrase = this.sourceListPhrase.size();
		this.lentmsourcePhrase = this.tmsourceListPhrase.size();
		
		this.lenDiff = Math.abs(this.sourceListPhrase.size()-this.tmsourceListPhrase.size());
		
	
	}
	public ArrayList<String> getEditHistory(){
		return this.editHistory;
	}
	
	public int getSourceLen(){
		return this.lensourcePhrase;
	}
	
	public int getTMSourceLen(){
		return this.lentmsourcePhrase;
	}
	
	public String getSource(){
		return this.source;
	}
	
	public String getTMSource(){
		return this.tmsource;
	}
	
	public int getlenDiff(){
		return this.lenDiff;
	}
	
	public ArrayList<String> getSourceList(){
		return this.sourceListPhrase;
	}
	
	public ArrayList<String> getTMSourceList(){
		return this.tmsourceListPhrase;
	}
	
	/**
	 * Upper bound of FMS based on length difference
	 * @return long containing the maxFMS
	 */
	public double getMaxFMS(){
		double div = (double)Math.max(getSourceLen(), getTMSourceLen());
		this.maxFMS = 1 - ((double)this.lenDiff / div);
		return this.maxFMS;
	}
	
	/**
	 * Optional function if we need to compare on a char basis
	 */
	public void computeSourceListChar(){
		
		String[] sourceChars = this.source.toLowerCase().split("");
		String[] tmsourceChars = this.tmsource.toLowerCase().split("");
		 
		this.sourceListChar = new ArrayList<String>(Arrays.asList(sourceChars));
		this.tmsourceListChar = new ArrayList<String>(Arrays.asList(tmsourceChars));
		
	}
	
	public ArrayList<String> getSourceListChar(){
		return this.sourceListChar;
	}
	
	public ArrayList<String> getTMSourceListChar(){
		return this.tmsourceListChar;
	} 
	

	/**
	 * Implementation for the EditDistance, O(words(a)*words(b)), as fast as possible as far as I know.
	 * Realization by a matrix which stores minimal distances.
	 * This might still be very slow for our purposes but we'll see.
	 * @return Edit or Levenshtein Distance for the two strings in the Set (source, TM source)
	 */
	public int getEditDistancePhrase(){
		
		int[][] distanceMat = new int[this.getSourceLen()+1][this.getTMSourceLen()+1];
		
	    for(int i=0; i<=this.getSourceLen(); i++){
	        distanceMat[i][0]= i;
	    }
	    
	    for(int j=0; j<=this.getTMSourceLen(); j++){
	        distanceMat[0][j]= j;
	    }
	    
	    for(int j=1; j<=this.getTMSourceLen(); j++){
	    	for(int i=1; i<=this.getSourceLen(); i++){
	    		if(this.getSourceList().get(i-1).equals(this.getTMSourceList().get(j-1))){
	    			distanceMat[i][j]=distanceMat[i-1][j-1];
	    		}else{
	    			 distanceMat[i][j]= minimum((distanceMat[i-1][j]+1),(distanceMat[i][j-1]+1),(distanceMat[i-1][j-1]+1));
	    		}
	    	}
	    }
		
		this.editDist = distanceMat[this.getSourceLen()][this.getTMSourceLen()];
	    return this.editDist;
	}

	/**
	 * Private function to provide the matrix to backtrack
	 * @return Edit Distance Matrix
	 */
	private void getEditDistanceMatrix(){
		
		int[][] distanceMat = new int[this.getSourceLen()+1][this.getTMSourceLen()+1];
		
	    for(int i=0; i<=this.getSourceLen(); i++){
	        distanceMat[i][0]= i;
	    }
	    
	    for(int j=0; j<=this.getTMSourceLen(); j++){
	        distanceMat[0][j]= j;
	    }
	    
	    for(int j=1; j<=this.getTMSourceLen(); j++){
	    	for(int i=1; i<=this.getSourceLen(); i++){
	    		if(this.getSourceList().get(i-1).equals(this.getTMSourceList().get(j-1))){
	    			distanceMat[i][j]=distanceMat[i-1][j-1];
	    		}else{
	    			 distanceMat[i][j]= minimum((distanceMat[i-1][j]+1),(distanceMat[i][j-1]+1),(distanceMat[i-1][j-1]+1));
	    		}
	    	}
	    }
		
		this.distanceMat = distanceMat;
	}
	
	/**
	 * recursive function to backtrack edit operations
	 * @param i
	 * @param j
	 * @return
	 */
	private ArrayList<String> backtrace(int i, int j){
		
		//INSERTION
		if(i>0 && this.distanceMat[i-1][j] + 1 == this.distanceMat[i][j]){
			editHistory.add(" Deletion ");
			return backtrace(i-1, j);
		}
		
		//DELETION
		if(j>0 && this.distanceMat[i][j-1] + 1 == this.distanceMat[i][j]){
			editHistory.add(" Insertion ");
			return backtrace(i, j-1);
		}
		if(i>0 && j>0 && this.distanceMat[i-1][j-1] + 1 == this.distanceMat[i][j]){
			editHistory.add(" Replacement ");
			return backtrace(i-1, j-1);
		}
		if(i>0 && j>0 && this.distanceMat[i-1][j-1]  == this.distanceMat[i][j]){
			editHistory.add(" Match ");
			return backtrace(i-1, j-1);
    	}
		return editHistory;
    }
    
    
	
	/**
	 * combining the matrix with the backtrace function to get an...
	 * @return ArrayList containing editHisory
	 */
	public ArrayList<String> getBacktrace(){
		this.editHistory = new ArrayList<String>();
		getEditDistanceMatrix();
    	backtrace(this.getSourceLen(),this.getTMSourceLen());
    	Collections.reverse(this.editHistory);
    	return this.editHistory;
	}
	
	
	
	private static int minimum(int a, int b, int c){
		return Math.min(Math.min(a, b),c);
	}
	
	
	/**
	 * TODO:Implementing the editDistance on a char basis, this will be a nightmare performance wise for longer sentences
	 * @return
	 */
	public int getEditDistanceChar(){
		return 0;
	}
	
	
	
}
